#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sobjc-commons/defines.h>
#include <sobjc-commons/str_list.h>
#include <sobjc-commons/utils.h>
#include "command.h"
#include "context.h"
#include "patch.h"

static void init_opts(objc_opt_list_t);
static char *process(const char *);
static char *preprocess(const char *);
static char *build_cpp_opts(const char *, objc_str_list_t *);
static char *translate(const char *);
static int compile(objc_opt_list_t);
static void build_compile_opts(objc_opt_list_t, objc_str_list_t *);
static void add_common_opts(objc_str_list_t *);
static void add_save_temps_opt(objc_str_list_t *);
static void add_load_path_opt(objc_str_list_t *);
static void add_lib_path_opt(objc_str_list_t *);
static void add_one_word_opt(objc_str_list_t *, const char *, objc_str_list_t);

int
main(int argc, char **argv)
{
  int i;
  char *file, *ext;
  objc_opt_list opts;

  objc_parse_opts(argc, argv);
  init_opts(&opts);
  for (i = 0; i < objc_main_opts.nfiles; i++) {
    file = objc_main_opts.files[i];
    ext = strrchr(file, '.');
    if (ext != NULL) {
      if (STREQ(ext, ".m"))
        opts.files[i] = process(file);
      else if (STREQ(ext, ".mi"))
        opts.files[i] = translate(file);
      else
        opts.files[i] = file;
    } else
      opts.files[i] = file;
  }
  opts.nfiles = i;

  if (objc_main_opts.does_compile) {
    if (opts.nfiles > 0)
      return compile(&opts);
    else {
      objc_print_error("no input files");
      return 1;
    }
  } else
    return 0;
}

static void
init_opts(objc_opt_list_t opts)
{
  opts->verbose = objc_main_opts.verbose;
  opts->files = ALLOC_N(char *, objc_main_opts.nfiles);
}

static char *
process(const char *file)
{
  char *ifile;
  FILE *fp;

  if ((fp = fopen(file, "r")) == NULL)
    objc_print_error("%s: No such file", file);
  fclose(fp);

  ifile = preprocess(file);
  if (objc_main_opts.does_translate)
    return translate(ifile);
  else
    return NULL;
}

static char *
preprocess(const char *file)
{
  objc_str_list_t argv;
  char *ifile;
  int st;

  argv = NULL;
  ifile = build_cpp_opts(file, &argv);
  st = objc_invoke_cmd_verbose(argv);
  if (st != 0)
    objc_print_error("preprocess failed");
  objc_str_list_free(argv);

  return ifile;
}

static char *
build_cpp_opts(const char *file, objc_str_list_t *argv)
{
  char *temp = NULL;

  objc_str_list_add_set(argv, "gcc");
  add_common_opts(argv);
  add_load_path_opt(argv);

  objc_str_list_add_set(argv, "-E");

  objc_str_list_add_set(argv, "-x"); 
  objc_str_list_add_set(argv, "none"); 
  objc_str_list_add_set(argv, "-D__OBJC__=1");
  objc_str_list_add_set(argv, "-D__SOBJC__=1");
  if (objc_main_opts.no_nil_recvs)
    objc_str_list_add_set(argv, "-DOBJC_NO_NIL_RECEIVERS=1");
  if (objc_main_opts.profile)
    objc_str_list_add_set(argv, "-DOBJC_PROFILE=1");

  objc_str_list_add_set(argv, file);
  if (objc_main_opts.does_translate) {
    objc_str_list_add_set(argv, ">");
    if (objc_main_opts.saves_temps) {
      temp = ALLOC_N(char, strlen(file)+2);
      strcpy(temp, file);
      temp[strlen(file)] = 'i';
      temp[strlen(file)+1] = 0;
    } else
      temp = objc_make_temp_name("sobjc_", ".mi");
    objc_str_list_add_set(argv, temp);
  }

  return temp;
}

static char *
translate(const char *ifile)
{
  char *cfile;
  objc_str_list_t args = NULL;

  objc_print_verbose("parse: %s", ifile);
  objc_parse_file(ifile);
  cfile = objc_add_ext_to_path(ifile, "c");
  objc_apply_patches(ifile, cfile);
  objc_write_init_code(cfile);

  if (!objc_main_opts.does_compile) {
    args = objc_str_list_add(NULL, "cat");
    args = objc_str_list_add(args, cfile);
    objc_invoke_cmd_verbose(args);
    objc_str_list_free(args);
  }

  return cfile;
}

static int
compile(objc_opt_list_t opts)
{
  objc_str_list_t argv = NULL;
  int st;

  objc_str_list_add_set(&argv, "gcc");
  build_compile_opts(opts, &argv);
  st = objc_invoke_cmd_verbose(argv);
  objc_str_list_free(argv);

  return st;
}

static void
build_compile_opts(objc_opt_list_t opts, objc_str_list_t *argv)
{
  int i;
  char temp[4];

  if (!objc_main_opts.does_link)
    objc_str_list_add_set(argv, "-c");
  else if (!objc_main_opts.does_asm)
    objc_str_list_add_set(argv, "-S");
  else if (!objc_main_opts.does_translate || !objc_main_opts.does_compile)
    objc_str_list_add_set(argv, "-E");

  add_common_opts(argv);
  add_load_path_opt(argv);
  add_lib_path_opt(argv);
  add_save_temps_opt(argv);

  if (objc_main_opts.does_compile) {
    if (objc_main_opts.debug)
      objc_str_list_add_set(argv, "-g");
    if (objc_main_opts.ansi)
      objc_str_list_add_set(argv, "-ansi");

    if (objc_main_opts.pedantic_errors)
      objc_str_list_add_set(argv, "-pedantic-errors");
    else if (objc_main_opts.w_pedantic)
      objc_str_list_add_set(argv, "-pedantic");

    sprintf(temp, "-O%d", objc_main_opts.optimize_lv);
    objc_str_list_add_set(argv, temp);
    objc_str_list_add_set(argv, "-finline-limit=100000");
  }

  if (objc_main_opts.output != NULL) {
    objc_str_list_add_set(argv, "-o");
    objc_str_list_add_set(argv, objc_main_opts.output);
  }

  for (i = 0; i < opts->nfiles; i++)
    objc_str_list_add_set(argv, opts->files[i]);

  if (objc_main_opts.does_link) {
    if (objc_main_opts.links_static)
      objc_str_list_add_set(argv, "-static");

    objc_str_list_add_set(argv, "-lsobjc-commons");
    if (objc_main_opts.profile)
      objc_str_list_add_set(argv, "-lsobjc-profile");
    else
      objc_str_list_add_set(argv, "-lsobjc");

    /*
    if (objc_main_opts.links_baustein)
      objc_str_list_add_set(argv, "-lbaustein");
    */
    add_one_word_opt(argv, "-l", objc_main_opts.libs);
  }
}

static void
add_common_opts(objc_str_list_t *argv)
{
  objc_str_list_t strs;
  char opt[64];

  if (objc_main_opts.cc_verbose)
    objc_str_list_add_set(argv, "-v");

  strs = objc_main_opts.flags;
  while (strs) {
    sprintf(opt, "-f%s", strs->car);
    objc_str_list_add_set(argv, opt);
    strs = strs->cdr;
  }

  if (objc_main_opts.no_warn)
    objc_str_list_add_set(argv, "-w");
  else {
    strs = objc_main_opts.warns;
    while (strs) {
      sprintf(opt, "-W%s", strs->car);
      objc_str_list_add_set(argv, opt);
      strs = strs->cdr;
    }
  }

  strs = objc_main_opts.compiler_opts;
  while (strs) {
    objc_str_list_add_set(argv, strs->car);
    strs = strs->cdr;
  }
}

static void
add_save_temps_opt(objc_str_list_t *argv)
{
  if (objc_main_opts.saves_temps)
    objc_str_list_add_set(argv, "--save-temps");
}

static void
add_load_path_opt(objc_str_list_t *argv)
{
  add_one_word_opt(argv, "-I", objc_main_opts.load_paths);
}

static void
add_lib_path_opt(objc_str_list_t *argv)
{
  add_one_word_opt(argv, "-L", objc_main_opts.lib_paths);
}

static void
add_one_word_opt(objc_str_list_t *argv, const char *opt, objc_str_list_t strs)
{
  const char *str;
  char *temp;
  size_t i;

  for (i = 0; i < objc_str_list_get_size(strs); i++) {
    str = objc_str_list_get(strs, i);
    temp = ALLOC_N(char, strlen(str)+3);
    sprintf(temp, "%s%s", opt, str);
    objc_str_list_add_set(argv, temp);
    objc_free(temp);
  }
}

